﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace Ndo.Text
{
    [AttributeUsage(AttributeTargets.Constructor)]
    public class ParsingPatternAttribute : Attribute
    {
        public ParsingPatternAttribute(string pattern)
            : this(pattern, "")
        { }

        public ParsingPatternAttribute(string pattern, string defaults)
        {
            this.pattern = pattern;
            this.defaults = defaults;
        }

        private string pattern;
        public string Pattern
        {
            get
            {
                return pattern;
            }
        }
        private string defaults;
        public string Defaults
        {
            get
            {
                return defaults;
            }
        }

        public string[] GetDefaults(int len)
        {
            string[] ss = new string[len];
            if (defaults.Length > 0)
            {
                int index = 0;
                for (int i = 0; i < len; i++)
                {
                    if (index >= 0)
                    {
                        int ii = index;
                        while ((ii = defaults.IndexOf(',', ii)) >= 0 && ++ii < defaults.Length && defaults[ii] == ',')
                            ii++;
                        if (ii < 0 || ii >= defaults.Length)
                        {
                            ss[i] = defaults.Substring(index);
                            index = -1;
                        }
                        else
                        {
                            ss[i] = defaults.Substring(index, ii - index - 1).Replace(",,", ",");
#if DEBUG
                            if (ss[i].IndexOf(",,") >= 0)
                                throw new Exception();
#endif
                            if (defaults[ii] == ' ')
                                ii++;
                            index = ii;
                        }
                    }
                    else
                    {
                        ss[i] = "";
                    }
                }
            }
            return ss;
        }

        public static Regex GetRegex<T>()
        {
            return GetRegex(typeof(T));
        }
        public static Regex GetRegex(Type type)
        {
            CFP cfp = GetCFP(type);
            if (cfp == null)
                return null;
            return cfp.F;
        }
        public static T Parse<T>(string text)
        {
            return (T)Parse(text, typeof(T));
        }
        public static object Parse(string text, Type type)
        {
            if (type == typeof(string))
                return text;
            CFP cfp = GetCFP(type);
            if (cfp == null)
                throw new InvalidOperationException();
            Match m = cfp.P.Match(text);
            if (!m.Success)
                throw new FormatException();
            ParameterInfo[] ps = cfp.C.GetParameters();
            object[] os = new object[ps.Length];
            for (int i = os.Length - 1; i >= 0; i--)
            {
                if (cfp.Ps[i])
                {
                    Group g = m.Groups["n" + (i + 1)];
                    if (g.Success)
                    {
                        os[i] = Parse(g.Value, ps[i].ParameterType);
                        continue;
                    }
                }
                if (cfp.Ds[i].Length > 0)
                    os[i] = Parse(cfp.Ds[i], ps[i].ParameterType);
                else
                    os[i] = Array.CreateInstance(ps[i].ParameterType, 1).GetValue(0);
            }
            return cfp.C.Invoke(os);
        }
        private class CFP
        {
            public ConstructorInfo C;
            public bool[] Ps;
            public string[] Ds;
            public Regex F;
            public Regex P;
        }
        private static CFP GetCFP(Type type)
        {
            CFP cfp;
            lock (cis)
            {
                if (cis.TryGetValue(type, out cfp))
                    return cfp;
            }
            foreach (ConstructorInfo c in type.GetConstructors(BindingFlags.Public | BindingFlags.Instance))
            {
                object[] os = c.GetCustomAttributes(typeof(ParsingPatternAttribute), false);
                if (os.Length != 0)
                {
                    ParsingPatternAttribute prpa = (ParsingPatternAttribute)os[0];
                    string s = prpa.pattern;
                    ParameterInfo[] ps = c.GetParameters();
                    string[] ss = new string[ps.Length], ss1 = new string[ps.Length];
                    cfp = new CFP();
                    cfp.Ps = new bool[ps.Length];
                    cfp.Ds = prpa.GetDefaults(ps.Length);
                    for (int i = 0; i < ps.Length; i++)
                    {
                        if (s.IndexOf("{" + i + "}") >= 0)
                        {
                            cfp.Ps[i] = true;
                            CFP css1 = GetCFP(ps[i].ParameterType);
                            if (css1 == null)
                            {
                                return null;
                            }
                            ss[i] = string.Format("{0}", css1.F);
                            ss1[i] = string.Format("(?<n{0}>{1})", i + 1, css1.F);
                        }
                    }
                    cfp.C = c;
                    if (ps.Length == 1 && ss[0] == null && ps[0].ParameterType == typeof(string))
                    {
                        cfp.Ps[0] = true;
                        cfp.F = new Regex("(" + s + ")", RegexOptions.Compiled);
                        cfp.P = new Regex("^(?<n1>(" + s + "))$", RegexOptions.Compiled);
                    }
                    else
                    {
                        cfp.F = new Regex("(" + string.Format(s, ss) + ")", RegexOptions.Compiled);
                        cfp.P = new Regex("^(" + string.Format(s, ss1) + ")$", RegexOptions.Compiled);
                    }
                    lock (cis)
                    {
                        cis[type] = cfp;
                    }
                    return cfp;
                }
            }
            lock (cis)
            {
                cis[type] = null;
            }
            return null;
        }
        private static Dictionary<Type, CFP> cis = new Dictionary<Type, CFP>();
    }
}
